 /*******************************************************************************
  * Copyright (c) 2000, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.part;

 import java.util.Arrays ;
 import java.util.List ;

 import org.eclipse.jface.action.Action;
 import org.eclipse.jface.action.IMenuManager;
 import org.eclipse.jface.action.IToolBarManager;
 import org.eclipse.jface.viewers.ISelectionChangedListener;
 import org.eclipse.jface.viewers.IStructuredSelection;
 import org.eclipse.jface.viewers.SelectionChangedEvent;
 import org.eclipse.jface.viewers.StructuredSelection;
 import org.eclipse.jface.viewers.TreeViewer;
 import org.eclipse.ui.ISharedImages;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.internal.IWorkbenchGraphicConstants;
 import org.eclipse.ui.internal.WorkbenchImages;
 import org.eclipse.ui.internal.WorkbenchMessages;

 /**
  * Implements a simple web style navigation metaphor for a <code>TreeViewer</code>.
  * Home, back, and "drill into" functions are supported for the viewer,
  * <p>
  * To use the <code>DrillDownAdapter</code> ..
  * </p>
  * <ul>
  * <li>Create an instance of <code>TreeViewer</code>. </li>
  * <li>Create a <code>DrillDownAdapter</code> for the viewer. </li>
  * <li>Create a container for your viewer with a toolbar or a popup menu.
  * Add actions for "goBack", "goHome", and "goInto" to either one by calling
  * </code>addNavigationActions</code> with the popup menu or toolbar.</li>
  * </ol>
  * <p>
  * If the input for the underlying viewer is changed by something other than the
  * adapter the <code>reset</code> method should be called. This will clear
  * the drill stack and update the navigation buttons to reflect the new
  * state of the underlying viewer.
  * </p>
  * </p>
  */
 public class DrillDownAdapter implements ISelectionChangedListener {
     private TreeViewer fChildTree;

     private DrillStack fDrillStack;

     private Action homeAction;

     private Action backAction;

     private Action forwardAction;

     /**
      * Allocates a new DrillDownTreePart.
      *
      * @param tree the target tree for refocusing
      */
     public DrillDownAdapter(TreeViewer tree) {
         fDrillStack = new DrillStack();
         fChildTree = tree;
     }

     /**
      * Adds actions for "go back", "go home", and "go into" to a menu manager.
      *
      * @param manager is the target manager to update
      */
     public void addNavigationActions(IMenuManager manager) {
         createActions();
         manager.add(homeAction);
         manager.add(backAction);
         manager.add(forwardAction);
         updateNavigationButtons();
     }

     /**
      * Adds actions for "go back", "go home", and "go into" to a tool bar manager.
      *
      * @param toolBar is the target manager to update
      */
     public void addNavigationActions(IToolBarManager toolBar) {
         createActions();
         toolBar.add(homeAction);
         toolBar.add(backAction);
         toolBar.add(forwardAction);
         updateNavigationButtons();
     }

     /**
      * Returns whether expansion is possible for the current selection. This
      * will only be true if it has children.
      *
      * @param element the object to test for expansion
      * @return <code>true</code> if expansion is possible; otherwise
      * return <code>false</code
      */
     public boolean canExpand(Object element) {
         return fChildTree.isExpandable(element);
     }

     /**
      * Returns whether "go back" is possible for child tree. This is only possible
      * if the client has performed one or more drilling operations.
      *
      * @return <code>true</code> if "go back" is possible; <code>false</code> otherwise
      */
     public boolean canGoBack() {
         return fDrillStack.canGoBack();
     }

     /**
      * Returns whether "go home" is possible for child tree. This is only possible
      * if the client has performed one or more drilling operations.
      *
      * @return <code>true</code> if "go home" is possible; <code>false</code> otherwise
      */
     public boolean canGoHome() {
         return fDrillStack.canGoHome();
     }

     /**
      * Returns whether "go into" is possible for child tree. This is only possible
      * if the current selection in the client has one item and it has children.
      *
      * @return <code>true</code> if "go into" is possible; <code>false</code> otherwise
      */
     public boolean canGoInto() {
         IStructuredSelection oSelection = (IStructuredSelection) fChildTree
                 .getSelection();
         if (oSelection == null || oSelection.size() != 1) {
             return false;
         }
         Object anElement = oSelection.getFirstElement();
         return canExpand(anElement);
     }

     /**
      * Create the actions for navigation.
      *
      * @param tree the target tree for refocusing
      */
     private void createActions() {
         // Only do this once.
 if (homeAction != null) {
             return;
         }

         // Home.
 homeAction = new Action(WorkbenchMessages.GoHome_text) {
             public void run() {
                 goHome();
             }
         };
         homeAction
                 .setToolTipText(WorkbenchMessages.GoHome_toolTip);
         homeAction
                 .setImageDescriptor(WorkbenchImages
                         .getImageDescriptor(IWorkbenchGraphicConstants.IMG_ETOOL_HOME_NAV));

         // Back.
 ISharedImages images = PlatformUI.getWorkbench().getSharedImages();
         backAction = new Action(WorkbenchMessages.GoBack_text) {
             public void run() {
                 goBack();
             }
         };
         backAction
                 .setToolTipText(WorkbenchMessages.GoBack_toolTip);
         backAction.setImageDescriptor(images
                 .getImageDescriptor(ISharedImages.IMG_TOOL_BACK));
         backAction.setDisabledImageDescriptor(images
                 .getImageDescriptor(ISharedImages.IMG_TOOL_BACK_DISABLED));

         // Forward.
 forwardAction = new Action(WorkbenchMessages.GoInto_text) {
             public void run() {
                 goInto();
             }
         };
         forwardAction.setToolTipText(WorkbenchMessages.GoInto_toolTip);
         forwardAction.setImageDescriptor(images
                 .getImageDescriptor(ISharedImages.IMG_TOOL_FORWARD));
         forwardAction.setDisabledImageDescriptor(images
                 .getImageDescriptor(ISharedImages.IMG_TOOL_FORWARD_DISABLED));

         // Update the buttons when a selection change occurs.
 fChildTree.addSelectionChangedListener(this);
         updateNavigationButtons();
     }

     /**
      * Expands the given items in the tree. The list of items passed should be
      * derived by calling <code>getExpanded</code>.
      *
      * @param items is a list of items within the tree which should be expanded
      */
     private void expand(List items) {
         fChildTree.setExpandedElements(items.toArray());
     }

     /**
      * Returns a list of elements corresponding to expanded nodes in
      * child tree.
      *
      * @return a list of expandd elements
      */
     private List getExpanded() {
         return Arrays.asList(fChildTree.getExpandedElements());
     }

     /**
      * Reverts the input for the tree back to the state when <code>goInto</code>
      * was last called.
      * <p>
      * A frame is removed from the drill stack. Then that frame is used to reset the
      * input and expansion state for the child tree.
      * </p>
      */
     public void goBack() {
         Object currentInput = fChildTree.getInput();
         DrillFrame oFrame = fDrillStack.goBack();
         Object input = oFrame.getElement();
         fChildTree.setInput(input);
         expand(oFrame.getExpansion());
         // if there was a selection, it should have been preserved,
 // but if not, select the element that was drilled into
 if (fChildTree.getSelection().isEmpty()) {
             fChildTree
                     .setSelection(new StructuredSelection(currentInput), true);
         }
         updateNavigationButtons();
     }

     /**
      * Reverts the input for the tree back to the state when the adapter was
      * created.
      * <p>
      * All of the frames are removed from the drill stack. Then the oldest frame is
      * used to reset the input and expansion state for the child tree.
      * </p>
      */
     public void goHome() {
         Object currentInput = fChildTree.getInput();
         DrillFrame oFrame = fDrillStack.goHome();
         Object input = oFrame.getElement();
         fChildTree.setInput(input);
         expand(oFrame.getExpansion());
         // if there was a selection, it should have been preserved,
 // but if not, select the element that was last drilled into
 if (fChildTree.getSelection().isEmpty()) {
             fChildTree
                     .setSelection(new StructuredSelection(currentInput), true);
         }
         updateNavigationButtons();
     }

     /**
      * Sets the input for the tree to the current selection.
      * <p>
      * The current input and expansion state are saved in a frame and added to the
      * drill stack. Then the input for the tree is changed to be the current selection.
      * The expansion state for the tree is maintained during the operation.
      * </p><p>
      * On return the client may revert back to the previous state by invoking
      * <code>goBack</code> or <code>goHome</code>.
      * </p>
      */
     public void goInto() {
         IStructuredSelection sel = (IStructuredSelection) fChildTree
                 .getSelection();
         Object element = sel.getFirstElement();
         goInto(element);
     }

     /**
      * Sets the input for the tree to a particular item in the tree.
      * <p>
      * The current input and expansion state are saved in a frame and added to the
      * drill stack. Then the input for the tree is changed to be <code>newInput</code>.
      * The expansion state for the tree is maintained during the operation.
      * </p><p>
      * On return the client may revert back to the previous state by invoking
      * <code>goBack</code> or <code>goHome</code>.
      * </p>
      *
      * @param newInput the new input element
      */
     public void goInto(Object newInput) {
         // If we can drill ..
 if (canExpand(newInput)) {
             // Save the old state.
 Object oldInput = fChildTree.getInput();
             List expandedList = getExpanded();
             fDrillStack.add(new DrillFrame(oldInput, "null", expandedList));//$NON-NLS-1$

             // Install the new state.
 fChildTree.setInput(newInput);
             expand(expandedList);
             updateNavigationButtons();
         }
     }

     /**
      * Resets the drill down adapter.
      * <p>
      * This method is typically called when the input for the underlying view
      * is reset by something other than the adapter.
      * On return the drill stack has been cleared and the navigation buttons
      * reflect the new state of the underlying viewer.
      * </p>
      */
     public void reset() {
         fDrillStack.reset();
         updateNavigationButtons();
     }

     /**
      * Updates the navigation buttons when a selection change occurs
      * in the tree.
      */
     public void selectionChanged(SelectionChangedEvent event) {
         updateNavigationButtons();
     }

     /**
      * Updates the enabled state for each navigation button.
      */
     protected void updateNavigationButtons() {
         if (homeAction != null) {
             homeAction.setEnabled(canGoHome());
             backAction.setEnabled(canGoBack());
             forwardAction.setEnabled(canGoInto());
         }
     }
 }

